/*
 * common.c
 *
 *  Created on: Nov 5, 2016
 *      Author: root
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include "common.h"

#define RULE_SEPARATOR    " or "
#define RULE_DELIMITER    " :: "
#define ORDER_DELIMITER    " > "

static char * dirname(char *path) {
	static const char dot[] = ".";
	char *last_slash;

	/* Find last '/'.  */
	last_slash = path != NULL ? strrchr(path, '/') : NULL;

	if (last_slash != NULL && last_slash != path && last_slash[1] == '\0') {
		/* Determine whether all remaining characters are slashes.  */
		char *runp;

		for (runp = last_slash; runp != path; --runp)
			if (runp[-1] != '/')
				break;

		/* The '/' is the last character, we have to look further.  */
		//if (runp != path)
		//	last_slash = __memrchr(path, '/', runp - path);
	}

	if (last_slash != NULL) {
		/* Determine whether all remaining characters are slashes.  */
		char *runp;

		for (runp = last_slash; runp != path; --runp)
			if (runp[-1] != '/')
				break;

		/* Terminate the path.  */
		if (runp == path) {
			/* The last slash is the first character in the string.  We have to
			 return "/".  As a special case we have to return "//" if there
			 are exactly two slashes at the beginning of the string.  See
			 XBD 4.10 Path Name Resolution for more information.  */
			if (last_slash == path + 1)
				++last_slash;
			else
				last_slash = path + 1;
		} else
			last_slash = runp;

		last_slash[0] = '\0';
	} else
		/* This assignment is ill-designed but the XPG specs require to
		 return a string containing "." in any case no directory part is
		 found and so a static and constant string is required.  */
		path = (char *) dot;

	return path;
}


static String get_real_path(String path) {
	char actualpath [PATH_MAX];
	char *ptr;

	if (path == NULL) {
		return NULL;
	}
	if (*path == '/') {
		return path;
	}
	ptr = realpath(path, actualpath);
    return copy_string(actualpath);
}

static void match_template(Statement *source) {
	State *start = NULL;
	Queue *repQueue = init_queue();
	Mapping *mapping = get_sys_mapping(get_mappings(source), MACRO_DEF_TEMPLATE);

	start = test_match(mapping, source, repQueue);
	if (start->accept) {
		eval_sys(mapping, repQueue);
	}
}

static String load(Statement *st, int type) {
	String file = copy_string(extract(st));
	String path = get_real_path(file);
	String dir_name = dirname(path);

	Statement *s = NULL;
	FILE *fp = NULL;

	fp = fopen( path, "rb");
	if (!fp)
		perror(path), exit(1);

	chdir(dir_name);

	while (has_next_statement(fp)) {
		s = next_statement(fp);

		switch (type) {
		case FILE_TYPE_TEMPLATE:
			match_template(s);
			break;
		case FILE_TYPE_FILE:
			match(s);
			break;
		case FILE_TYPE_PACKAGE:
			//TODO
			break;
		default:
			fprintf(stderr, "File types not defined!\n");
			return NULL;
		}
	}

	fclose(fp);

	return NULL;
}

String rule(Statement *s1, Statement *s2) {
    String name = copy_string(extract(s1));
    String pattern = copy_string(extract(s2));
    String* patterns = split_strings(pattern, RULE_SEPARATOR);
    String p = NULL;
    Pattern* pat = NULL;
    Queue* queue = init_queue();
    while (*patterns) {
        p = inner_string(*patterns);
        String* str = split_strings(p, RULE_DELIMITER);
        pat = init_pattern(*str, *(str + 1));
        enqueue(queue, pat);
        patterns++;
    }
    register_rule(name, queue);
    return NULL;
}

String group(Statement *s1, Statement *s2) {
    String name = copy_string(extract(s1));
    String pattern = copy_string(extract(s2));
    String* patterns = split_strings(pattern, RULE_SEPARATOR);
    String p = NULL;
    Pattern* pat = NULL;
    Queue* queue = init_queue();
    while (*patterns) {
        p = inner_string(*patterns);
        String* str = split_strings(p, RULE_DELIMITER);
        pat = init_pattern(*str, *(str + 1));
        enqueue(queue, pat);
        patterns++;
    }
    register_group(name, queue);
    return NULL;
}

String token(Statement *s1, Statement *s2) {
    String name = copy_string(extract(s1));
    String pattern = copy_string(extract(s2));
    register_token(name, pattern);
	return NULL;
}

String lex(Statement *s) {
    String lex_orders = inner_string(copy_string(extract(s)));
    lexOrders = split_strings(lex_orders, ORDER_DELIMITER);
    cache_patterns();
    return NULL;
}

String extract(Statement *s) {
	if (s->type != ARG)
		return NULL;
	String str = substr_between(to_string_statement(s), PAREN_L, PAREN_R);
	return str;
}

String arglist(Statement *s) {
	QueueNode *node = NULL;
	Statement *st = NULL;
	String result = init_string();

	node = s->children->head;
	while (node != NULL) {
		st = (Statement *) node->element;
		if (st->type == ARG) {
			result = dyna_strcat(result, extract(st));
			result = dyna_strcat(result, " ");
		}
		node = node->next;
	}
	return trim_string(result);
}

String target(Statement *s) {
	QueueNode *node = NULL;
	Statement *st = NULL;
	String result = init_string();

	node = s->children->head;
	while (node != NULL) {
		st = (Statement *) node->element;
		if (st->type == ARG) {
			result = dyna_strcat(result, to_string_statement(st));
			result = dyna_strcat(result, " ");
		}
		node = node->next;
	}
	return trim_string(result);
}

String funcname(Statement *s) {
	String temp = copy_string(extract(s));
	char *p = strchr(temp, '(');
	if (p) {
		*p = ' ';
		p = trim_string(p);
		temp = p;
		p = strchr(temp, ' ');
		if (p)
			*p = '\0';
		return temp;
	} else {
		return NULL;
	}
}

String signature(Statement *s) {
	//TODO
	return NULL;
}

String symname(Statement *s) {
	//TODO
	return NULL;
}

String loadtemplate(Statement *s) {
	return load(s, FILE_TYPE_TEMPLATE);
}

String loadfile(Statement *s) {
	return load(s, FILE_TYPE_FILE);
}

String loadpackage(Statement *s) {
	return load(s, FILE_TYPE_PACKAGE);
}

void init_macro_functions() {
    macros = hashmap_new();
    hashmap_put(macros, "EXTRACT", extract);
    hashmap_put(macros, "TARGET", target);
    hashmap_put(macros, "FUNCNAME", funcname);
    hashmap_put(macros, "SIGNATURE", signature);
    hashmap_put(macros, "SYMNAME", symname);
    hashmap_put(macros, "LOADTEMPLATE", loadtemplate);
    hashmap_put(macros, "LOADFILE", loadfile);
    hashmap_put(macros, "LOADPACKAGE", loadpackage);
    hashmap_put(macros, "DUMPCODE", dumpcode);
    hashmap_put(macros, "DUMPHIERARCHY", dumphierarchy);
    hashmap_put(macros, "DUMPCLASS", dumpclass);
    hashmap_put(macros, "LEX", lex);
    hashmap_put(macros, "RULE", rule);
    hashmap_put(macros, "GROUP", group);
    hashmap_put(macros, "TOKEN", token);
}

void *get_macro_function(String name) {
    void *macro;
    hashmap_get(macros, name, &macro);
    return macro;

    /*
	if (strcmp(name, "EXTRACT") == 0) {
		return extract;
	} else if (strcmp(name, "TARGET") == 0) {
		return target;
	} else if (strcmp(name, "FUNCNAME") == 0) {
		return funcname;
	} else if (strcmp(name, "SIGNATURE") == 0) {
		return signature;
	} else if (strcmp(name, "SYMNAME") == 0) {
		return symname;
	} else if (strcmp(name, "LOADTEMPLATE") == 0) {
		return loadtemplate;
	} else if (strcmp(name, "LOADFILE") == 0) {
		return loadfile;
	} else if (strcmp(name, "LOADPACKAGE") == 0) {
		return loadpackage;
	} else if (strcmp(name, "DUMPCODE") == 0) {
		return dumpcode;
	} else if (strcmp(name, "DUMPHIERARCHY") == 0) {
		return dumphierarchy;
	} else if (strcmp(name, "DUMPCLASS") == 0) {
		return dumpclass;
	} else if (strcmp(name, "LEX") == 0) {
        return lex;
    } else if (strcmp(name, "RULE") == 0) {
        return rule;
    } else if (strcmp(name, "GROUP") == 0) {
        return group;
    } else if (strcmp(name, "TOKEN") == 0) {
        return token;
    }

	return NULL;
    */
}


String macro_function(String functionName, int argc, Statement **argv) {
	MacroFunc1 func1;
	MacroFunc2 func2;
	MacroFunc3 func3;
	MacroFunc4 func4;

	switch(argc) {
		case 1:
			func1 = (MacroFunc1)get_macro_function(functionName);
			return func1(*argv);
		case 2:
			func2 = (MacroFunc2)get_macro_function(functionName);
			return func2(*argv, *(argv + 1));
		case 3:
			func3 = (MacroFunc3)get_macro_function(functionName);
			return func3(*argv, *(argv + 1), *(argv + 2));
		case 4:
			func4 = (MacroFunc4)get_macro_function(functionName);
			return func4(*argv, *(argv + 1), *(argv + 3), *(argv + 4));
		default:
			return NULL;
	}
	return NULL;
}
